/**
 * createStore(reducer, preloadedState, enhancer)
 * {getState, dispatch, subscribe}
 *
 * 当createStore只传递2个参数时 返回一个标准的store对象
 * 当传递enhancer函数时
 * 会返回一个带有参数createStore的函数
 * 在该函数中又返回了一个带有前2个参数的的函数
 * 在该函数中实现自定义的store
 * 最终返回该store
 *
 * reducer 函数 需要执行的操作
 * preloadedState 容器 存放初始的state
 * enhancer 函数（可选）增强store的方法， 例如重写dispatch函数 使其可用于异步操作 返回一个store
 */
function createStore(reducer, preloadedState, enhancer) {
  if (typeof reducer !== 'function') throw TypeError('reducer need a function')

  if (enhancer) {
    if (typeof enhancer !== 'function') {
      throw TypeError('enhancer must a function')
    }
    // 调用enhancer传递createStore 再调用enhancer返回的函数传入前两个参数
    // 外界通过对enhancer返回函数的编写 实现自定义的store
    return enhancer(createStore)(reducer, preloadedState)
  }

  let currentState = preloadedState
  // 存放订阅者函数
  let currentListeners = []
  // 获取state
  function getState() {
    return currentState
  }
  // dispatch 用于调用对应action的reducer
  function dispatch(action) {
    console.log('dispatch')
    if (!isPlainObject(action)) throw TypeError('action is not a object')

    if (!action.type) throw Error('action must have attrbute [type]')

    currentState = reducer(currentState, action)

    // 只有在调用action时 store 才会变化 所以在此调用订阅者
    // 循环数组，调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      let listener = currentListeners[i]
      listener()
    }
  }
  // 通过subscribe创建对store的监听
  function subscribe(listener) {
    currentListeners.push(listener)
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}
/**
 * applyMiddleware = enhancer
 * applyMiddleware(logger, thunk)
 * @param  {function} middleware
 *
 * 当存在中间件时 需要先执行中间件 最后再到reducer
 * 中间件的使用方法是
 * next 在这里很关键
 * next 指代的是下一个中间件 直至指向dispatch
 * const logger = store => next => action => {
 *   // TODO
 *   next(action)
 * }
 */
function applyMiddleware(...middleware) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      let store = createStore(reducer, preloadedState)

      let middlewareAPI = {
        dispatch: store.dispatch,
        getState: store.getState
      }
      //  通过map调用多个中间件的第一层函数 传递 middlewareAPI
      let chian = middleware.map(middleware => middleware(middlewareAPI))

      let dispatch = compose(chian)(store.dispatch)
      return {
        ...store,
        dispatch
      }
    }
  }
}

/**
 * 使用方式
 * var actions = bindActionCreators({ increment, decrement }, store.dispatch)
 * store.dispatch({ type: "increment" }); => actions.increment()
 */
function bindActionCreators(actions, dispatch) {
  let obj = {}
  for (const key in actions) {
    obj[key] = () => {
      dispatch(actions[key]())
    }
  }
  return obj
}

/**
 *   var rootReducer = combineReducers({ counter: counterReducer })
 */
function combineReducers(reducers) {
  let reducerKeys = Object.keys(reducers)
  for (let i = 0; i < reducerKeys.length; i++) {
    let key = reducerKeys[i]
    // 判断reducer是不是函数
    if (typeof reducers[key] !== 'function') throw new Error('reducer is not a function')
  }
  // 调用没哟个reducer 将其中的状态储存到一个新的对象中并返回
  return function(state, action) {
    let nextState = {}
    for (let i = 0; i < reducerKeys.length; i++) {
      var key = reducerKeys[i]
      var reducer = reducers[key]
      nextState[key] = reducer(state[key], action)
    }
    return nextState
  }
}


function isPlainObject(obj) {
  // 判断obj是否是对象，且不等于null
  if (typeof obj !== 'object' || obj === null) return
  // 区分对象和数组，当前原型 === 最顶层原型 如果相等则一定是对象
  var proto = obj
  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto)
  }
  // 循环结束时 proto 为顶层原型
  return Object.getPrototypeOf(obj) === proto
}

/**
 * @param {*} chian
 * @returns
 */
function compose(chian) {
  return function (dispatch) {
    /**
     * 在这里使用倒序的原因是我们需要中间件在dispatch之前执行
     * 将dispatch传入函数 形成链式调用
     * 最终返回的dispatch为第一个中间件
     * next => action => {
     *  next(action)
     * }
     * next就是下一个中间件 如：
     * thunk中的next值为logger
     * logger中的next值为dispatch
     *
     * 在外界调用 dispacth时 就会变成
     * store.dispatch({type: 'XXXX'})
     * thunk => logger => dispatch
     * 通过 next(action) 对action的传递 在最终的dispatch中也能获得action的值
     */

    for (let i = chian.length - 1; i >= 0; i--) {
      dispatch = chian[i](dispatch)
    }
    return dispatch
  }
}
